import logging
import re
from pathlib import Path

import networkx as nx
from tqdm import tqdm

from .baseparser import Parser
from .graphfunc import find_and_merge_cycles
from .pathsolver import iter_path, filter_c_headers
from .projectloader import CProjectLoader


class ProjectParser(Parser):
    def __init__(self, project: CProjectLoader, progress=False):
        super().__init__()
        self._progress = progress
        self.logger = logging.getLogger("parser")

        self.project = project
        self.project_path = project.project_path

        self.files = iter_path(self.project_path)
        self.headers = filter_c_headers(self.files)
        self.build_dependency_graph()

        # 删除自循环
        self.graph.remove_edges_from(nx.selfloop_edges(self.graph))

        self.origin_graph = self.graph.copy()

        # 合并循环图
        self.graph = find_and_merge_cycles(self.graph, self._merge_node)

    @staticmethod
    def _merge_node(graph: nx.DiGraph, scc):
        new_node = ', '.join(sorted(str(n) for n in scc))
        new_node = f'({new_node})'
        nodes_data = []
        for node_id in scc:
            node_data = graph.nodes[node_id]
            node_data['name'] = node_id
            nodes_data.append(node_data)
        new_node_data = {
            'type': f'module_group',
            'name': list(map(lambda d: d['name'], nodes_data)),
            'c_file': list(map(lambda d: d['c_file'], nodes_data)),
            'h_file': list(map(lambda d: d['h_file'], nodes_data))
        }
        return new_node, new_node_data


    @staticmethod
    def _get_name(file: Path):
        if file is None:
            return None
        return file.stem

    def get_module_name(self, h_file, c_file):
        h_name = self._get_name(h_file)
        c_name = self._get_name(c_file)
        if h_name is None and c_name is None:
            return None
        if h_name is None:
            return c_name
        return h_name

    def build_dependency_graph(self):
        self.graph = nx.DiGraph()
        files = self.files
        if self._progress:
            files = tqdm(self.files, desc="Building File Dependency Graph")
        for h_file, c_file in files:
            name = self.get_module_name(h_file, c_file)
            self.graph.add_node(name, type='module',
                                h_file=str(h_file) if h_file else None,
                                c_file=str(c_file) if c_file else None)
            includes = set()
            if h_file is not None:
                h_includes = self.extract_includes(h_file)
                includes.update(h_includes)
            if c_file is not None:
                c_includes = self.extract_includes(c_file)
                includes.update(c_includes)
            for include in includes:
                include_name = include.name
                if include_name in self.headers:
                    include_name = self.get_module_name(*self.headers[include_name])
                    if name == include_name:
                        continue
                    self.graph.add_edge(name, include_name)

    def extract_includes(self, filename: Path):
        includes = []
        pattern = re.compile(r'^\s*#\s*include\s*([<"])([^">]+)[">]')
        file_content = self.project.get_file_content(filename)
        for line in file_content:
            match = pattern.match(line)
            if match:
                include_type = match.group(1)
                header_file = match.group(2)
                # 不考虑规范书写
                includes.append(Path(header_file))
                # 如果规范书写
                # if include_type == '"':
                #     includes.append(Path(header_file))
        return includes
